<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    /* 
深度克隆
1). 大众乞丐版
    问题1: 函数属性会丢失
    问题2: 循环引用会出错
2). 面试基础版本
    解决问题1: 函数属性还没丢失
3). 面试加强版本
    解决问题2: 循环引用正常
4). 面试加强版本2(优化遍历性能)
    数组: while | for | forEach() 优于 for-in | keys()&forEach() 
    对象: for-in 与 keys()&forEach() 差不多
*/

    const obj = {
      a: {
        m: 1
      },
      b: [2],
      c: 3,
      fn: function () {}
    }
    obj.a.c = obj.b
    obj.b[1] = obj.a
    /* 
    1). 大众乞丐版
      问题1: 函数属性会丢失
      问题2: 循环引用会出错
      请求返回的数据是没有这2个问题

      请求返回的数据对象的拷贝可以用这种方式
    */
    function deepClone1(target) {
      return JSON.parse(JSON.stringify(target))
    }

    // console.log(deepClone1(obj))

    

    /* 
    获取数据的类型字符串名
    */
    function getType(data) {
      // data.toString()
      return Object.prototype.toString.call(data).slice(8, -1) // -1代表最后一位
      //[object Object] [object Array]  ===> Array  [object Object] ==> Object

      // {m: 1}.toString() ==> '[object Object]'
      // [2, 3].toString() ==> '2,3'  ==> '[object Array]'
    }

    /*
    2). 面试基础版本
      解决问题1: 函数属性还没丢失
      循环引用的对象会报错==> 原因: 同一个对象会被不断拷贝对象 => 同一个对象只需要被拷贝一次
    */
    function deepClone2(target) {
      const type = getType(target)

      if (type === 'Object' || type === 'Array') {
        const cloneTarget = type === 'Array' ? [] : {}
        for (const key in target) {
          if (target.hasOwnProperty(key)) {
            cloneTarget[key] = deepClone2(target[key])  // 递归调用实现深拷贝
          }
        }
        return cloneTarget
      } else {
        return target
      }
    }
    // console.log(deepClone2(obj))

    /* 
    3). 面试加强版本
      解决问题2: 循环引用正常
      key是被拷贝对象， value是拷贝产生的对象

      源对象 / 克隆对象
    */
    function deepClone3(target, map=new Map()) {
      const type = getType(target)

      if (type === 'Object' || type === 'Array') {
        // 在准备创建clone对象前, 优先考虑缓存中的
        let cloneTarget = map.get(target)
        if (cloneTarget) {
          return cloneTarget
        }
        // 缓存中没有, 创建新的clone对象
        cloneTarget = type === 'Array' ? [] : {}
        // 一旦产生一人新的clone对象, 就缓存到map中
        map.set(target, cloneTarget)

        for (const key in target) {
          if (target.hasOwnProperty(key)) {
            cloneTarget[key] = deepClone3(target[key], map)
          }
        }
        return cloneTarget
      } else {
        return target
      }
    }
    // console.log(deepClone3(obj))
    
    /* 
    4). 面试加强版本2(优化遍历性能)
        数组: while | for | forEach() 优于 for-in | keys()&forEach() 
        对象: for-in 与 keys()&forEach() 差不多
    */
    function deepClone4(target, map = new Map()) {
      const type = getType(target)
      if (type === 'Object' || type === 'Array') {
        let cloneTarget = map.get(target)
        if (cloneTarget) {
          return cloneTarget
        }

        if (type === 'Array') {
          cloneTarget = []
          map.set(target, cloneTarget)
          target.forEach((item, index) => {
            cloneTarget[index] = deepClone4(item, map)
          })
        } else {
          cloneTarget = {}
          map.set(target, cloneTarget)
          Object.keys(target).forEach(key => {
            cloneTarget[key] = deepClone4(target[key], map)
          })
        }

        return cloneTarget
      } else {
        return target
      }
    }
  </script>
</body>

</html>